const PCM = {
    urlRegex: /(https?:\/\/[^\s]+)/g,

    packageTypes: {
        "plugin": {
            tabname: "Plugins"
        },
        "library": {
            tabname: "Libraries"
        },
        "colortheme": {
            tabname: "Color themes"
        },
    },

    packages: [],
    repoUrl: "",

    currentFilter: "",
    currentPackageType: "",

    initPackageTypes: function () {
        var packageTypes = document.getElementById("package-types");

        for (var type of Object.keys(this.packageTypes)) {
            var button = document.createElement("button");
            button.classList.add("nav-link");
            button.innerText = this.packageTypes[type].tabname;

            button.onclick = function (type) {
                if (this.currentPackageType === type) return;
                if (this.currentPackageType) this.packageTypes[this.currentPackageType].element.classList.remove("active");
                this.packageTypes[type].element.classList.add("active");
                this.currentPackageType = type;
                this.showPackages();
            }.bind(this, type);

            this.packageTypes[type].element = button;
            packageTypes.appendChild(button);
        }

        this.packageTypes["plugin"].element.onclick();
    },

    searchChange: function (value) {
        this.currentFilter = value;
        this.showPackages();
    },

    createPackageCard: function (pkg) {
        var card = document.createElement("div");
        card.classList.add("card", "flex-row", "border-dark");

        var img_container = document.createElement("div");
        img_container.classList.add("m-3", "d-flex", "justify-content-center", "align-items-center");
        img_container.style = "min-width: 64px";

        var img = document.createElement("img");
        if (pkg.has_icon) {
            img.src = `/pcm/icon?repourl=${encodeURIComponent(this.repoUrl)}&pkgid=${pkg.identifier}`;
        } else {
            img.src = `/static/img/box.svg`;
        }
        img_container.appendChild(img);

        card.appendChild(img_container);

        var textContainer = document.createElement("div");
        textContainer.classList.add("card-body", "ps-0");

        var name = document.createElement("h6");
        name.classList.add("card-title");
        name.innerText = pkg.name;

        var description = document.createElement("p");
        description.classList.add("card-text");
        description.innerText = pkg.description;

        textContainer.appendChild(name);
        textContainer.appendChild(description);

        card.appendChild(textContainer);

        return card;
    },

    setPackageDetails: function (pkg) {
        var details = document.getElementById("package-details");
        while (details.firstChild) details.removeChild(details.firstChild);

        var append = (e, type, text) => {
            var t = document.createElement(type);
            var html = text.replace(this.urlRegex, (url) => {
                return `<a href="${url}" target="_blank">${url}</a>`;
            });
            t.insertAdjacentHTML("beforeend", html);
            e.appendChild(t);
        }

        append(details, "h4", pkg.name);

        for (var paragraph of pkg.description_full.split("\n")) {
            append(details, "p", paragraph);
        }

        append(details, "h6", "Metadata");

        // id, license, tags, author, maintainer, resources
        var list = document.createElement("ul");
        append(list, "li", `Package identifier: ${pkg.identifier}`);
        append(list, "li", `License: ${pkg.license}`);

        if (pkg.tags)
            append(list, "li", `Tags: ${pkg.tags.join(", ")}`);

        var appendContact = (element, dict, key) => {
            append(element, "li", `${key}: ${dict.name}`);
            var nested = document.createElement("ul");

            for (var k in dict.contact) {
                var v = dict.contact[k];
                if (k === "email")
                    nested.insertAdjacentHTML("beforeend", `email: <a href='mailto:${v}'>${v}</a>`);
                else
                    append(nested, "li", `${k}: ${v}`);
            }

            element.appendChild(nested);
        }

        appendContact(list, pkg.author, "Author");

        if (pkg.maintainer)
            appendContact(list, pkg.maintainer, "Maintainer");

        if (pkg.resources) {
            append(list, "li", "Resources");
            var nested = document.createElement("ul");

            for (var k in pkg.resources) {
                var v = pkg.resources[k];
                if (k === "email")
                    nested.insertAdjacentHTML("beforeend", `email: <a href='mailto:${v}'>${v}</a>`);
                else
                    append(nested, "li", `${k}: ${v}`);
            }

            list.appendChild(nested);
        }

        details.appendChild(list);

        // Versions
        var table = document.createElement("table");
        table.classList.add("table");
        var thead = document.createElement("thead");
        var tr = document.createElement("tr");

        for (var name of ["Version", "Download size", "Install size", "Status", "Platforms"])
            append(tr, "th", name);

        thead.appendChild(tr);
        table.appendChild(thead);
        var tbody = document.createElement("tbody");

        var toReadableSize = (size) => {
            if (!size) return "-";
            if (size > 1024 * 1024) return `${(size / 1024.0 / 1024.0).toFixed(1)} Mb`;
            if (size > 1024) return `${Math.floor(size / 1024)} Kb`;
            return `${size} b`;
        }

        var appendImg = (element, src) => {
            var img = document.createElement("img");
            img.src = src;
            img.classList.add("m-1");
            img.style = "width: 1em; height: 1em;";
            element.appendChild(img);
        }

        for (var version of pkg.versions) {
            tr = document.createElement("tr")

            var td = document.createElement("td");
            var a = document.createElement("a");
            a.href = version.download_url;
            appendImg(a, "/static/img/download.svg");

            td.appendChild(a);
            append(td, "span", version.version);

            tr.appendChild(td);

            append(tr, "td", toReadableSize(version.download_size));
            append(tr, "td", toReadableSize(version.install_size));
            append(tr, "td", version.status);

            td = document.createElement("td");

            if (!pkg.platforms) pkg.platforms = ["windows", "linux", "macos"];

            if (pkg.platforms.includes("windows"))
                appendImg(td, "/static/img/windows.svg");
            if (pkg.platforms.includes("macos"))
                appendImg(td, "/static/img/apple.svg");
            if (pkg.platforms.includes("linux"))
                appendImg(td, "/static/img/tux.svg");

            tr.appendChild(td);

            tbody.appendChild(tr);
        }

        table.appendChild(tbody);
        details.appendChild(table);
    },

    getPackageRank: function (pkg) {
        if (this.currentFilter.length <= 2) return 1;

        var terms = this.currentFilter.toLowerCase().split(" ").filter((token) => token != "");
        var rank = 0;

        var findTermMatches = (str) => {
            var result = 0;
            var lower = str.toLowerCase();

            for (var term of terms)
                if (lower.includes(term))
                    result += 1;

            return result;
        };

        // Match on package id
        if (terms.length === 1 && terms[0] == pkg.identifier)
            rank += 10000;

        if (terms.length === 1 && findTermMatches(pkg.identifier))
            rank += 1000;

        // Match on package name
        rank += 500 * findTermMatches(pkg.name);

        // Match on tags
        if (pkg.tags) {
            for (var tag of pkg.tags)
                rank += 100 * findTermMatches(tag);
        }

        // Match on package description
        rank += 10 * findTermMatches(pkg.description);
        rank += 10 * findTermMatches(pkg.description_full);

        // Match on author/maintainer
        rank += 3 * findTermMatches(pkg.author.name);

        if (pkg.maintainer)
            rank += 3 * findTermMatches(pkg.maintainer.name);

        // Match on resources
        for (var [key, value] of Object.entries(pkg.resources)) {
            rank += findTermMatches(key);
            rank += findTermMatches(value);
        }

        // Match on license
        if (terms.length === 1 && terms[0] == pkg.license)
            rank += 1;

        return rank;
    },

    showPackages: function () {
        var div = document.getElementById("package-cards");

        while (div.firstChild) div.removeChild(div.firstChild);

        var packages = this.packages.filter((pkg) => pkg.type === this.currentPackageType);

        var packageRanks = [];

        for (var i in packages) {
            var rank = this.getPackageRank(packages[i]);

            // Packages with no versions are delisted and should not be shown
            if (packages[i].versions.length === 0)
                rank = 0;

            packageRanks.push([rank, parseInt(i)]);
        }

        // Sort by descending rank, ascending index
        packageRanks = packageRanks.sort(
            (a, b) => {
                if (a[0] != b[0]) return b[0] - a[0];
                return a[1] - b[1];
            });

        for (var [rank, index] of packageRanks) {
            if (rank == 0) break;

            var packageCard = this.createPackageCard(packages[index]);
            packageCard.onclick = this.setPackageDetails.bind(this, packages[index]);
            div.appendChild(packageCard);
        }
    },

    showLoading: function (show) {
        var element = document.getElementById("loading");
        element.classList.remove(show ? "d-none" : "d-flex");
        element.classList.add(show ? "d-flex" : "d-none");
    },

    showAlert: function (text, error) {
        var element = document.getElementById("alert");

        if (error) element.classList.add("alert-warning");
        else element.classList.remove("alert-warning");

        element.innerText = text;
        element.classList.add("show");

        if (this.alertTimeout) clearTimeout(this.alertTimeout);

        this.alertTimeout = setTimeout(() => {
            this.alertTimeout = null;
            element.classList.remove("show");
        }, error ? 10000 : 2000);
    },

    loadRepository: function () {
        var url = document.getElementById("repository-url").value;
        var error = UrlValidator.check(url);
        if (error) {
            this.showAlert(error, true);
            return;
        }

        this.showLoading(true);
        this.repoUrl = url;
        fetch(`/pcm/packages?repourl=${encodeURIComponent(url)}`)
            .then((response) => {
                this.showLoading(false);
                if (response.ok) {
                    response.json().then((json) => {
                        if ("error" in json) {
                            this.showAlert(json.error, true);
                        } else {
                            this.packages = json.packages;
                            this.showAlert(`Loaded repository\n${json.repo.name}`);
                            this.showPackages();
                        }
                    });
                } else if (response.status == 422) {
                    response.json().then((json) => {
                        this.showAlert(json.detail[0].msg);
                    }).catch((error) => {
                        this.showAlert("Server error");
                        console.log(`Error parsing json output of 422 response: ${error}`);
                    });
                } else {
                    this.showAlert("Server error");
                    console.log(`Server returned ${response.status}, see network logs for details.`);
                }
            }).catch((error) => {
                this.showLoading(false);
                this.showAlert(`Error fetching repository:\n${error}`, true);
            });
    }
}

window.onload = () => {
    document.getElementById("repository-url").onkeyup = (event) => {
        if (event.code == "Enter") {
            event.preventDefault();
            PCM.loadRepository();
        }
    }
    PCM.initPackageTypes();
    PCM.loadRepository();
}
